package com.funcity.net
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.ObjectEncoding;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	public class Connection extends EventDispatcher
	{
		// connection state
		private static const READ_HEADER:uint = 0;
		private static const READ_CONTENT:uint = 1;

		// packet definition
		private static const HEADER_LENGTH:uint = 4;
		private static const FLAG_BITS:uint = 8;
		private static const FLAG_MASK:uint = 0xFF;

		// statistic
		private var _output:Number = 0;
		private var _input:Number = 0;

		private var _socket:Socket = new Socket();
		private var _state:uint = READ_HEADER;
		private var _header:uint = 0;

		public function Connection()
		{
			super(null);

			_socket.endian = Endian.BIG_ENDIAN;
			_socket.objectEncoding = ObjectEncoding.AMF3;
		}
		
		public function get output():Number
		{
			return _output;
		}

		public function get input():Number
		{
			return _input;
		}

		public function connect(host:String, port:uint):void
		{
			if(!_socket.connected)
			{
				addEventListeners();
				_socket.connect(host, port);
			}
		}

		public function close():void
		{
			if(_socket.connected)
			{
				_socket.close();
			}
		}

		public function send(flag:uint, packet:ByteArray):void
		{
			if(_socket.connected)
			{
				_socket.writeUnsignedInt((packet.length << FLAG_BITS) | flag);
				_socket.writeBytes(packet);
				_socket.flush();
				
				_output += packet.length;
			}
		}

		protected function readPacket(flag:uint, buffer:ByteArray):void
		{
		}

		private function addEventListeners():void
		{
			_socket.addEventListener(Event.CONNECT, onConnect);
			_socket.addEventListener(Event.CLOSE, onClose);
			_socket.addEventListener(ProgressEvent.SOCKET_DATA, onReceiveData);
			
			_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			_socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
		}

		private function removeEventListeners():void
		{
			_socket.removeEventListener(Event.CLOSE, onClose);
			_socket.removeEventListener(ProgressEvent.SOCKET_DATA, onReceiveData);
			_socket.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
		}

		private function onConnect(event:Event):void
		{
			_socket.removeEventListener(Event.CONNECT, onConnect);
			_socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			
			dispatchEvent(event);
		}
		
		private function onClose(event:Event):void
		{
			removeEventListeners();
			
			dispatchEvent(event);
		}

		private function onReceiveData(event:ProgressEvent):void
		{
			while (true)
			{
				if (_state == READ_HEADER)
				{
					if(_socket.bytesAvailable < HEADER_LENGTH)
					{
						return;
					}
					_header = _socket.readUnsignedInt();
					_state = READ_CONTENT;
					continue;
				}
				else
				{
					var packetLength:uint = _header >> FLAG_BITS;
					if (_socket.bytesAvailable < packetLength)
					{
						return;
					}
					var buffer:ByteArray = new ByteArray();
					buffer.endian = Endian.BIG_ENDIAN;
					buffer.objectEncoding = ObjectEncoding.AMF3;

					_socket.readBytes(buffer, 0, packetLength);
					_input += buffer.length;
					_state = READ_HEADER;
					
					readPacket(_header & FLAG_MASK, buffer);
					continue;
				}
			}
		}
		
		private function onSecurityError(event:SecurityErrorEvent):void
		{
			removeEventListeners();
			
			dispatchEvent(event);			
		}
		
		private function onIOError(event:IOErrorEvent):void
		{
			removeEventListeners();
			
			dispatchEvent(event);
		}
	}
}